﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.IO
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.IO;
    using System.IO.IsolatedStorage;
    using System.Linq;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    using Phuse.Text;
    using Phuse.UnitTesting;

    /// <summary>Tests the <see cref="Base64Stream"/> class.</summary>
    [TestClass]
    public sealed class StreamTest : TestBase
    {
        private static readonly int OriginalSizeBytes =
            1 << (Environment.OSVersion.Platform == PlatformID.WinCE ? 15 : 20);

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Tests the main <see cref="Base64Stream"/> use case.</summary>
        [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Test code, will not be localized.")]
        [TestMethod]
        public void Base64MainTest()
        {
            const int RevolutionCount = 3;

            AssertThrow<ArgumentOutOfRangeException>(() => Base64Stream.GetEncodedLength(-1));
            AssertThrow<ArgumentOutOfRangeException>(() => Base64Stream.GetMaxOriginalLength(-1));

            // Make sure all code is loaded/jitted before measuring.
            // 749 original bytes translate to 1025 encoded bytes (including "\r\n" pairs). This leads to the default
            // buffer being filled at least once. For the decode case, the flushing happens inside Dispose, which
            // increases code coverage.
            this.Base64Decode(749);
            this.Base64Encode(749);

            int totalSizeBytes = 0;
            int decodingDurationMilliseconds = 0;
            int encodingDurationMilliseconds = 0;

            for (int offset = 0; offset < RevolutionCount; ++offset)
            {
                int sizeBytes = OriginalSizeBytes - offset;
                totalSizeBytes += sizeBytes;
                decodingDurationMilliseconds += this.Base64Decode(sizeBytes);
                encodingDurationMilliseconds += this.Base64Encode(sizeBytes);
            }

            Console.WriteLine(
                "Base64 decoding throughput: {0} KB/s", ((double)totalSizeBytes) / decodingDurationMilliseconds);
            Console.WriteLine(
                "Base64 encoding throughput: {0} KB/s", ((double)totalSizeBytes) / encodingDurationMilliseconds);
        }

        /// <summary>Tests the main <see cref="QuotedPrintableStream"/> use cases.</summary>
        [TestMethod]
        public void QuotedPrintableMainTest()
        {
            byte[] originalArray = this.GetRandomQuotedPrintableBytes(1024);
            AssertQPDecodeResult(originalArray, originalArray);

            // Remove trailing white space
            AssertQPDecodeResult("One", "One \t    \t");
            AssertQPDecodeResult("2\r\n", "2 \t  \t \r\n");
            AssertQPDecodeResult("Three\r\n4", "Three  \t \r\n4   ");
            AssertQPDecodeResult("Five6", "Five=   \r\n6 \t ");

            // Soft vs. hard line break
            AssertQPDecodeResult("Hello\r\nthere.", "Hello\r\nthere.");
            AssertQPDecodeResult("Calling you.", "Calling =\r\nyou.");
            AssertQPDecodeResult("Are you there?", "Are you=\r\n there?");

            // Quoted chars
            AssertQPDecodeResult("This is the _= sign.", "This is the _=3D sign.");
            AssertQPDecodeResult("Adding some space   ", "Adding some space=20=20=20");

            // CR without LF (illegal but allowed on a best effort basis)
            AssertQPDecodeResult("Bla bla    \rmore bla bla.", "Bla bla    \rmore bla bla.");
            AssertQPDecodeResult("Oops \r", "Oops \r");

            // Lowercase hex quoting (illegal but suggested for robust implementation)
            AssertQPDecodeResult("The = sign again.", "The =3d sign again.");

            // Unquoted = sign (illegal but suggested for robust implementation)
            AssertQPDecodeResult("The = sign again.", "The = sign again.");
            AssertQPDecodeResult("The =", "The =");

            // Incomplete quoting (illegal but suggested for robust implementation)
            AssertQPDecodeResult("The =5 sign once more.", "The =5 sign once more.");
            AssertQPDecodeResult("The =8", "The =8");
            AssertQPDecodeResult("The =8\r\n", "The =8  \r\n");
            AssertQPDecodeResult("=F\r\n=x\r\n  5", "=F\r\n=x  \r\n  =\r\n5");
            AssertQPDecodeResult("The =7xd sign for the last time.", "The =7xd sign for the last time.");

            // RFC2047
            AssertQPDecodeResult("Th= quick=brown fox=7  ", "Th=_quick=brown_fox=7__", true);
            AssertQPDecodeResult("jumps over the lazy=dog.", "jumps_over_the_lazy=3Ddog.", true);

            this.EncodeDecode(false);
            this.EncodeDecode(true);
        }

        /// <summary>Tests the main <see cref="FlowedStream"/> use cases.</summary>
        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "MemoryStream objects do not need to be disposed.")]
        [TestMethod]
        public void FlowedStreamMainTest()
        {
            new FlowedStream(new MemoryStream(), false, false, 1).ToString();
            AssertThrow<ArgumentOutOfRangeException>(
                () => new FlowedStream(new MemoryStream(), false, false, 0).Dispose());

            ////                     Input            keep all         keep quoted      delete all       delete quoted
            AssertFlowedDecodeResult("One \r\n Two",  "One Two",       "One \r\n Two",  "OneTwo",        "One \r\n Two");
            AssertFlowedDecodeResult("One\r\n Two",   "One\r\nTwo",    "One\r\n Two",   "One\r\nTwo",    "One\r\n Two");
            AssertFlowedDecodeResult("One \r\n>Two",  "One\r\n>Two",   "One \r\n>Two",  "One\r\n>Two",   "One \r\n>Two");
            AssertFlowedDecodeResult("> -- \r\n X",   ">-- \r\nX",     ">-- \r\n X",    ">-- \r\nX",     ">-- \r\n X");
            AssertFlowedDecodeResult("-- \r5",        "-- \r\n5",      "-- \r\n5",      "-- \r\n5",      "-- \r\n5");
            AssertFlowedDecodeResult(" -x  y \r\n5",  "-x  y 5",       " -x  y \r\n5",  "-x  y5",        " -x  y \r\n5");

            AssertFlowedEncodeResult(
                "As easy as ABC",
                "As easy \r\nas ABC",
                "As easy as ABC",
                "As easy  \r\nas ABC",
                "As easy as ABC",
                9);

            AssertFlowedEncodeResult(
                ">>One Two \r\nThree Four",
                ">> One \r\n>> Two \r\nThree \r\nFour",
                ">> One \r\n>> Two \r\nThree Four",
                ">> One  \r\n>> Two \r\nThree  \r\nFour",
                ">> One  \r\n>> Two \r\nThree Four",
                9);

            AssertFlowedEncodeResult(
                "One Two Three\r\n",
                "One \r\nTwo \r\nThree\r\n",
                "One Two Three\r\n",
                "One  \r\nTwo  \r\nThree\r\n",
                "One Two Three\r\n",
                1);

            AssertFlowedEncodeResult(
                "One\r\nTwo Three\r\n",
                "One\r\nTwo \r\nThree\r\n",
                "One\r\nTwo Three\r\n",
                "One\r\nTwo  \r\nThree\r\n",
                "One\r\nTwo Three\r\n",
                1);

            AssertFlowedEncodeResult(string.Empty, string.Empty, string.Empty, string.Empty, string.Empty, 1);
            AssertFlowedEncodeResult("From ", " From ", "From ", " From ", "From ", 1);
            AssertFlowedEncodeResult("From \r\n", " From \r\n", "From \r\n", " From \r\n", "From \r\n", 1);
            AssertFlowedEncodeResult(" Hello", "  Hello", " Hello", "  Hello", " Hello", 8);
        }

        /// <summary>Tests the main <see cref="FixLineEndingsStream"/> use case.</summary>
        [TestMethod]
        public void FixLineEndingsMainTest()
        {
            AssertLineEndingsFixed("\r\n \r\n", "\n \n");
            AssertLineEndingsFixed("\r\n\r\n\r\n", "\n\n\n");

            AssertLineEndingsFixed("\r\n \r\n", "\r \r");
            AssertLineEndingsFixed("\r\n\r\n\r\n", "\r\r\r");

            AssertLineEndingsFixed("\r\n \r\n", "\n \r");
            AssertLineEndingsFixed("\r\n\r\n\r\n", "\n\r\r");

            AssertLineEndingsFixed("\r\n \r\n", "\r\n \r\n");
            AssertLineEndingsFixed("\r\n\r\n\r\n", "\r\n\r\n\r\n");
        }

        /// <summary>Measures the throughput of <see cref="FixLineEndingsStream"/>.</summary>
        [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Test code, will not be localized.")]
        [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "FixLineEndingsStream", Justification = "Class name.")]
        [TestMethod]
        public void FixLineEndingsPerformanceTest()
        {
            byte[] originalBuffer = new byte[OriginalSizeBytes];
            this.Random.NextBytes(originalBuffer);

            using (var originalStream = new MemoryStream(originalBuffer))
            using (var fixedLineEndings = new FixLineEndingsStream(originalStream))
            {
                fixedLineEndings.ReadByte(); // Make sure all code is loaded/jitted before measuring
                byte[] readBuffer = new byte[1024];
                int start = Environment.TickCount;

                while (fixedLineEndings.Read(readBuffer, 0, readBuffer.Length) > 0)
                {
                }

                int duration = Environment.TickCount - start;
                Console.WriteLine(
                    "FixLineEndingsStream throughput: {0} KB/s", (double)originalBuffer.Length / duration);
                Assert.AreEqual(originalBuffer.Length, originalStream.Position);
            }
        }

        /// <summary>Tests <see cref="SegmentStream"/>.</summary>
        [TestMethod]
        public void SegmentStreamTest()
        {
            using (var underlyingStream = new MemoryStream())
            {
                AssertThrow<ArgumentNullException>(() => new SegmentStream(null).Dispose());
                AssertThrow<ArgumentException>(() => new SegmentStream(new Base64Stream(underlyingStream)).Dispose());
                AssertThrow<ArgumentOutOfRangeException>(() => new SegmentStream(underlyingStream, -1, long.MaxValue).Dispose());
                AssertThrow<ArgumentOutOfRangeException>(() => new SegmentStream(underlyingStream, 0, -1).Dispose());
            }

            var originalBytes = new byte[128];
            Random.NextBytes(originalBytes);

            for (int offset = 0; offset < originalBytes.Length * 2; ++offset)
            {
                TestRead(originalBytes, offset, Random.Next(0, originalBytes.Length));
            }

            using (var stream = new MemoryStream(originalBytes, false))
            {
                AuxiliaryMethodsTest<SegmentStream>(stream);
            }
        }

        /// <summary>Tests auxiliary methods, mostly whether they throw the correct exceptions.</summary>
        [TestMethod]
        public void AuxiliaryMethodsTest()
        {
            AuxiliaryMethodsTest<Base64Stream>();
            AuxiliaryMethodsTest<QuotedPrintableStream>();
            AuxiliaryMethodsTest<FlowedStream>();
            AuxiliaryMethodsTest<FixLineEndingsStream>();
            AuxiliaryMethodsTest<ForwardingStream>();
        }

        /// <summary>Tests <see cref="StreamHelper"/>.</summary>
        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "MemoryStream objects do not need to be disposed.")]
        [TestMethod]
        public void StreamHelperTest()
        {
            byte[] buffer = new byte[1];

            AssertThrow<ArgumentNullException>(
                () => StreamHelper.Copy(null, new MemoryStream(), buffer),
                () => StreamHelper.Copy(new MemoryStream(), null, buffer),
                () => StreamHelper.Copy(new MemoryStream(), new MemoryStream(), null));
            AssertThrow<ArgumentException>(
                () => StreamHelper.Copy(new MemoryStream(), new MemoryStream(), new byte[0]));

            var disposedStream = new MemoryStream();
            disposedStream.Dispose();
            AssertThrow<ObjectDisposedException>(
                () => StreamHelper.Copy(disposedStream, new MemoryStream(), buffer),
                () => StreamHelper.Copy(new MemoryStream(new byte[] { 42 }), disposedStream, buffer));
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private int Base64Decode(int originalSizeBytes)
        {
            byte[] original = new byte[originalSizeBytes];
            this.Random.NextBytes(original);
            int start;
            byte[] decoded = new byte[original.Length];

            using (var encodedStream = new MemoryStream(EncodingObjects.ASCII.GetBytes(Convert.ToBase64String(original))))
            {
                start = Environment.TickCount;

                using (var decodingStream = new Base64Stream(encodedStream))
                {
                    Assert.AreEqual(original.Length, ReadToEnd(decodingStream, decoded, 0, decoded.Length));
                    Assert.AreEqual(0, decodingStream.Read(decoded, 0, decoded.Length));
                }
            }

            int duration = Environment.TickCount - start;
            CollectionAssert.AreEqual(original, decoded);
            return duration;
        }

        private int Base64Encode(int originalSizeBytes)
        {
            byte[] original = new byte[originalSizeBytes];
            this.Random.NextBytes(original);
            int start;
            MemoryStream encodedStream;
            int encodedLength = Base64Stream.GetEncodedLength(original.Length);
            Assert.AreEqual(
                encodedLength, Base64Stream.GetEncodedLength(Base64Stream.GetMaxOriginalLength(encodedLength)));

            using (encodedStream = new MemoryStream(encodedLength))
            {
                start = Environment.TickCount;

                using (var encodingStream = new Base64Stream(encodedStream))
                {
                    encodingStream.Write(original, 0, original.Length);
                }
            }

            int duration = Environment.TickCount - start;
            byte[] encoded = encodedStream.ToArray();
            Assert.AreEqual(encodedLength, encoded.Length);
            CollectionAssert.AreEqual(
                original, Convert.FromBase64String(EncodingObjects.ASCII.GetString(encoded, 0, encoded.Length)));
            return duration;
        }

        private void EncodeDecode(bool rfc2047Mode)
        {
            var original = new byte[4096];
            this.Random.NextBytes(original);
            int encodedLength = QuotedPrintableStream.GetEncodedLength(original, 0, original.Length, rfc2047Mode);

            using (var stream = new MemoryStream(encodedLength))
            using (var quotedPrintableStream = new QuotedPrintableStream(stream, rfc2047Mode))
            {
                quotedPrintableStream.Write(original, 0, original.Length);
                quotedPrintableStream.Flush();
                Assert.AreEqual(encodedLength, stream.Length);
                stream.Seek(0, SeekOrigin.Begin);
                var converted = new byte[original.Length + 1]; // Make sure that we'd detect an overlong stream
                Assert.AreEqual(original.Length, ReadToEnd(quotedPrintableStream, converted, 0, converted.Length));
                Array.Resize(ref converted, original.Length);
                CollectionAssert.AreEqual(original, converted);
            }
        }

        private byte[] GetRandomQuotedPrintableBytes(int length)
        {
            byte[] array = new byte[length];

            for (int index = 0; index < array.Length;)
            {
                byte candidate = (byte)this.Random.Next(byte.MaxValue + 1);

                if ((candidate != (byte)'=') && (candidate != (byte)' ') && (candidate != (byte)'\t'))
                {
                    array[index++] = candidate;
                }
            }

            return array;
        }

        private static void AssertQPDecodeResult(
            string expectedDecoded, string encoded, params object[] additionalStreamCtorArgs)
        {
            AssertReadResult<QuotedPrintableStream>(expectedDecoded, encoded, additionalStreamCtorArgs);
        }

        private static void AssertQPDecodeResult(
            byte[] expectedDecoded, byte[] encoded, params object[] additionalStreamCtorArgs)
        {
            AssertReadResult<QuotedPrintableStream>(expectedDecoded, encoded, additionalStreamCtorArgs);
        }

        private static void AssertFlowedDecodeResult(
            string input, string keepAllExpected, string keepQuotedExpected, string delAllExpected, string delQuotedExpected)
        {
            AssertReadResult<FlowedStream>(keepAllExpected, input, false, false);
            AssertReadResult<FlowedStream>(keepQuotedExpected, input, false, true);
            AssertReadResult<FlowedStream>(delAllExpected, input, true, false);
            AssertReadResult<FlowedStream>(delQuotedExpected, input, true, true);
        }

        private static void AssertFlowedEncodeResult(
            string input, string keepAllExpected, string keepQuotedExpected, string delAllExpected, string delQuotedExpected, int encodedCharactersPerLine)
        {
            AssertWriteResult<FlowedStream>(keepAllExpected, input, 10, false, false, encodedCharactersPerLine);
            AssertWriteResult<FlowedStream>(keepQuotedExpected, input, 10, false, true, encodedCharactersPerLine);
            AssertWriteResult<FlowedStream>(delAllExpected, input, 10, true, false, encodedCharactersPerLine);
            AssertWriteResult<FlowedStream>(delQuotedExpected, input, 10, true, true, encodedCharactersPerLine);
        }

        private static void AssertLineEndingsFixed(string expectedFixed, string original)
        {
            AssertReadResult<FixLineEndingsStream>(expectedFixed, original);
            AssertWriteResult<FixLineEndingsStream>(expectedFixed, original, 5);
        }

        private static void AssertReadResult<TStream>(
            string expectedConverted, string original, params object[] additionalStreamCtorArgs) where TStream : Stream
        {
            AssertReadResult<TStream>(
                EncodingObjects.ASCII.GetBytes(expectedConverted),
                EncodingObjects.ASCII.GetBytes(original),
                additionalStreamCtorArgs);
        }

        private static void AssertReadResult<TStream>(
            byte[] expectedConverted, byte[] original, params object[] additionalStreamCtorArgs) where TStream : Stream
        {
            using (var originalStream = new MemoryStream(original))
            using (var stream =
                Activator<TStream>.CreateInstance(PrependArgument(originalStream, additionalStreamCtorArgs)))
            {
                byte[] convertedArray = new byte[expectedConverted.Length];
                Assert.AreEqual(expectedConverted.Length, ReadToEnd(stream, convertedArray, 0, convertedArray.Length));
                Assert.AreEqual(0, stream.Read(convertedArray, 0, convertedArray.Length));
                CollectionAssert.AreEqual(expectedConverted, convertedArray);
            }
        }

        private static void AssertWriteResult<TStream>(
            string expectedConverted, string original, params object[] additionalStreamCtorArgs) where TStream : Stream
        {
            AssertWriteResult<TStream>(
                EncodingObjects.ASCII.GetBytes(expectedConverted),
                EncodingObjects.ASCII.GetBytes(original),
                additionalStreamCtorArgs);
        }

        private static void AssertWriteResult<TStream>(
            byte[] expectedConverted, byte[] original, params object[] additionalStreamCtorArgs) where TStream : Stream
        {
            using (var memoryStream = new MemoryStream())
            {
                using (var stream = Activator<TStream>.CreateInstance(
                    PrependArgument(memoryStream, additionalStreamCtorArgs)))
                {
                    stream.Write(original, 0, original.Length);
                }

                CollectionAssert.AreEqual(expectedConverted, memoryStream.ToArray());
            }
        }

        private static void TestRead(byte[] originalBytes, int offset, int count)
        {
            using (var original = new MemoryStream(originalBytes))
            using (var segment = new SegmentStream(original, offset, count))
            {
                var readBytes = new byte[originalBytes.Length];
                int read;
                int total = 0;

                while ((read = segment.Read(readBytes, total, readBytes.Length - total)) > 0)
                {
                    total += read;
                }

                Assert.IsTrue(Enumerable.SequenceEqual(originalBytes.Skip(offset).Take(count), readBytes.Take(total)));
            }
        }

        private static object[] PrependArgument(object arg0, object[] additionalArgs)
        {
            var args = new object[additionalArgs.Length + 1];
            args[0] = arg0;
            Array.Copy(additionalArgs, 0, args, 1, additionalArgs.Length);
            return args;
        }

        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Disposable objects are disposed in the methods they are passed to.")]
        private static void AuxiliaryMethodsTest<TStream>() where TStream : Stream
        {
            AssertThrow<ArgumentNullException>(() => Activator<TStream>.CreateInstance((Stream)null));

            byte[] byteArray = new byte[] { (byte)'a', (byte)'3', (byte)'F' };

            if (typeof(TStream).GetConstructor(new Type[] { typeof(MemoryStream), typeof(int) }) != null)
            {
                AssertThrow<ArgumentOutOfRangeException>(
                    () => Activator<TStream>.CreateInstance(new MemoryStream(byteArray), 0));
            }

            AuxiliaryMethodsTest<TStream>(new MemoryStream(byteArray, false));
            AuxiliaryMethodsTest<TStream>(new MemoryStream(byteArray, true));

#if WINDOWS_PHONE
            using (var isolated = IsolatedStorageFile.GetUserStoreForApplication())
            {
                AuxiliaryMethodsTest<TStream>(
                    new IsolatedStorageFileStream("Test.txt", FileMode.OpenOrCreate, FileAccess.Write, isolated));
            }
#else
            AuxiliaryMethodsTest<TStream>(
                new FileStream("Test.txt", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None));
#endif

            // An exception thrown out of Dispose must not be propagated.
            using (Stream stream = Activator<TStream>.CreateInstance(new ThrowingDisposeStream()))
            {
                if (stream.CanRead)
                {
                    stream.ReadByte();
                }

                if (stream.CanWrite)
                {
                    stream.WriteByte(42);
                }
            }
        }

        private static void AuxiliaryMethodsTest<TStream>(Stream underlyingStream) where TStream : Stream
        {
            TStream stream;

            using (stream = Activator<TStream>.CreateInstance(underlyingStream))
            {
                Assert.AreEqual(underlyingStream.CanRead, stream.CanRead);
                Assert.AreEqual(underlyingStream.CanWrite, stream.CanWrite);
                Assert.AreEqual(false, stream.CanSeek);
                stream.Flush();

                AssertThrow<NotSupportedException>(
                    () => stream.Position.ToString(CultureInfo.InvariantCulture),
                    () => stream.Position = 0,
                    () => stream.Length.ToString(CultureInfo.InvariantCulture),
                    () => stream.SetLength(2),
                    () => stream.Seek(0, SeekOrigin.Begin));

                if (stream.CanRead)
                {
                    stream.ReadByte();
                    Assert.AreEqual(underlyingStream.CanRead, stream.CanRead);
                }
                else
                {
                    AssertThrow<NotSupportedException>(() => stream.ReadByte());
                }

                underlyingStream.Seek(0, SeekOrigin.Begin);

                using (stream = Activator<TStream>.CreateInstance(underlyingStream))
                {
                    if (stream.CanWrite)
                    {
                        stream.WriteByte(41);
                        Assert.IsTrue(stream.CanWrite);
                        stream.Flush();
                        stream.WriteByte(42);

                        // Disposing the underlying stream before the stream will lead to an ObjectDisposedException
                        // being thrown internally when leaving the using block. However, said exception must not make it
                        // out of Dispose().
                        underlyingStream.Dispose();
                    }
                    else
                    {
                        AssertThrow<NotSupportedException>(() =>
                            {
                                stream.WriteByte(41);
                                stream.WriteByte(42);
                                stream.Flush();
                            });
                    }
                }
            }

            Assert.AreEqual(false, stream.CanRead);
            Assert.AreEqual(false, stream.CanWrite);
            Assert.AreEqual(false, stream.CanSeek);

            AssertThrow<ObjectDisposedException>(() => stream.ReadByte());
            AssertThrow<ObjectDisposedException>(() => stream.Flush());
        }

        private static int ReadToEnd(Stream stream, byte[] buffer, int offset, int count)
        {
            int originalCount = count;
            int pastEnd = offset + count;
            int read;

            while ((offset < pastEnd) && ((read = stream.Read(buffer, offset, count)) > 0))
            {
                offset += read;
                count -= read;
            }

            return originalCount - count;
        }

        /// <summary>Represents a stream that throws from <see cref="Stream.Dispose()"/>.</summary>
        private sealed class ThrowingDisposeStream : BufferStream
        {
            /// <summary>See <see cref="Stream.Read"/>..</summary>
            public sealed override int Read(byte[] buffer, int offset, int count)
            {
                return 0;
            }

            /// <summary>See <see cref="Stream.Write"/>.</summary>
            public override void Write(byte[] buffer, int offset, int count)
            {
            }

            /// <summary>See <see cref="Stream.Dispose(bool)"/>.</summary>
            [SuppressMessage("Microsoft.Usage", "CA2215:Dispose methods should call base class dispose", Justification = "Intentional.")]
            protected sealed override void Dispose(bool disposing)
            {
                throw new NotImplementedException();
            }

            [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "MemoryStream objects do not need to be disposed.")]
            internal ThrowingDisposeStream() : base(new MemoryStream(), 1024)
            {
            }
        }
    }
}
